{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import pandas as pd\n",
    "import yfinance as yf\n",
    "import seaborn as sns\n",
    "from sklearn import linear_model\n",
    "from sklearn.svm import SVC\n",
    "from sklearn.naive_bayes import GaussianNB\n",
    "from sklearn.ensemble import RandomForestClassifier\n",
    "from sklearn.neural_network import MLPClassifier\n",
    "import datetime\n",
    "import backtrader as bt\n",
    "from backtrader.feeds import PandasData\n",
    "import backtrader.analyzers as btanalyzers"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "plt.style.use('seaborn-colorblind')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "ticker = 'NIO'\n",
    "start = datetime.datetime(2020, 1, 1)\n",
    "end = datetime.datetime(2020, 12, 30)\n",
    "stock = yf.download(ticker, progress=True, actions=True, start=start, end=end)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "stock = stock['Adj Close']\n",
    "stock = pd.DataFrame(stock)\n",
    "stock"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "stock.rename(columns={\"Adj Close\": ticker}, inplace=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "stock.head(4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "stock['returns'] = np.log(stock/stock.shift(1))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "stock"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "stock.dropna(inplace=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "stock['direction'] = np.sign(stock['returns']).astype(int)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "stock"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "lags = [1, 2,3, 4,5]\n",
    "cols = []\n",
    "for lag in lags:\n",
    "    col = f'rnt_lag{lag}'\n",
    "    stock[col] = stock['returns'].shift(lag)\n",
    "    cols.append(col)\n",
    "stock.dropna(inplace=True)\n",
    "stock.head(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def create_bins(data, bins=[0]):\n",
    "    global cols_bin\n",
    "    cols_bin = []\n",
    "    for col in cols:\n",
    "        col_bin = col + '_bin'\n",
    "        data[col_bin] = np.digitize(data[col], bins=bins)\n",
    "        cols_bin.append(col_bin)\n",
    "\n",
    "create_bins(stock)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "stock.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "models = {\n",
    "    'log_reg': linear_model.LogisticRegression(),\n",
    "    'gauss_nb': GaussianNB(),\n",
    "    'svm': SVC(),\n",
    "    'random_forest': RandomForestClassifier(max_depth=10, n_estimators=100),\n",
    "    'MLP': MLPClassifier(max_iter=100)\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def fit_models(data):\n",
    "    mfit = {model: models[model].fit(data[cols_bin], data['direction']) for model in models.keys()}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def derive_positions(data):\n",
    "    for model in models.keys():\n",
    "        data['pos_' + model] = models[model].predict(data[cols_bin])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def evaluate_strats(data):\n",
    "    global strategy_rtn\n",
    "    strategy_rtn = []\n",
    "    for model in models.keys():\n",
    "        col = 'strategy_' + model\n",
    "        data[col] = data['pos_'+ model] * data['returns']\n",
    "        strategy_rtn.append(col)\n",
    "    strategy_rtn.insert(0, 'returns')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "fit_models(stock)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "derive_positions(stock)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "evaluate_strats(stock)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "stock[strategy_rtn].sum().apply(np.exp)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "price = yf.download(ticker, progress=True, actions=True, start=start,end=end)\n",
    "price.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "predictions = stock['strategy_random_forest']\n",
    "predictions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "predictions = pd.DataFrame(predictions)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "predictions.rename(columns={'strategy_random_forest': 'predicted'}, inplace=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "price = predictions.join(price, how='right').dropna()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "price.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "predictions.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "OHLCH=['open', 'high', 'low', 'close', 'volume']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class SignalData(PandasData):\n",
    "    cols = OHLCH + ['predicted']\n",
    "    lines = tuple(cols)\n",
    "    params = {c: -1 for c in cols}\n",
    "    params.update({'datetime': None})\n",
    "    params = tuple(params.items())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class RandomForestStrategy(bt.Strategy):\n",
    "    params = dict(\n",
    "    \n",
    "    )\n",
    "    \n",
    "    def log(self, txt, dt=None):\n",
    "        ''' Logging function fot this strategy'''\n",
    "        dt = dt or self.data.datetime[0]\n",
    "        if isinstance(dt, float):\n",
    "            dt = bt.num2date(dt)\n",
    "        print('%s, %s' % (dt.isoformat(), txt))\n",
    "    \n",
    "    def __init__(self):\n",
    "        self.data_predicted = self.datas[0].predicted\n",
    "        self.data_open = self.datas[0].open\n",
    "        self.data_close = self.datas[0].close\n",
    "        \n",
    "        self.order = None\n",
    "        self.price = None\n",
    "        self.comm = None\n",
    "        \n",
    "    def notify_order(self, order):\n",
    "        if order.status in [order.Submitted, order.Accepted]:\n",
    "            return\n",
    "        if order.status in [order.Completed]:\n",
    "            if order.isbuy():\n",
    "                self.log('BUY EXECUTED, %.2f' % order.executed.price)\n",
    "            elif order.issell():\n",
    "                self.log('SELL EXECUTED, %.2f' % order.executed.price)\n",
    "            self.bar_executed = len(self)\n",
    "        elif order.status in [order.Canceled, order.Margin, order.Rejected]:\n",
    "            self.log('Order Canceled/Margin/Rejected')\n",
    "        self.order = None\n",
    "\n",
    "    def notify_trade(self, trade):\n",
    "        if not trade.isclosed:\n",
    "            return\n",
    "\n",
    "        self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %\n",
    "                 (trade.pnl, trade.pnlcomm))\n",
    "    \n",
    "    def next_open(self):\n",
    "        if not self.position:\n",
    "            if self.data_predicted > 0:\n",
    "                size = int((self.broker.getcash()/self.datas[0].open)*0.9)\n",
    "                self.log(f\"BUY CREATED - SIZE : {size}, CASH: {self.broker.getcash():.2f}, OPEN: {self.data_open[0]}, CLOSE: {self.data_close[0]}\")\n",
    "                self.buy(size=size)\n",
    "        else:\n",
    "            if self.data_predicted < 0:\n",
    "                self.log(f'SELL CREATED - SIZE: {self.position.size}')\n",
    "                self.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "data = SignalData(dataname=price)\n",
    "cerebro = bt.Cerebro(cheat_on_open=True)\n",
    "cerebro.addstrategy(RandomForestStrategy)\n",
    "cerebro.adddata(data, name=ticker)\n",
    "cerebro.broker.setcash(10000.0)\n",
    "cerebro.broker.setcommission(commission=0.0003)\n",
    "\n",
    "cerebro.addanalyzer(btanalyzers.SharpeRatio, _name=\"sharpe\")\n",
    "cerebro.addanalyzer(btanalyzers.DrawDown, _name=\"drawdown\")\n",
    "cerebro.addanalyzer(btanalyzers.Returns, _name=\"returns\")\n",
    "\n",
    "print('the beigin value is {:.2f}'.format(cerebro.broker.getvalue()))\n",
    "\n",
    "back = cerebro.run()\n",
    "\n",
    "print('the end value is {:.2f}'.format(cerebro.broker.getvalue()))\n",
    "\n",
    "\n",
    "par_list = [[x.analyzers.returns.get_analysis()['rtot'],\n",
    "             x.analyzers.returns.get_analysis()['rnorm100'],\n",
    "             x.analyzers.drawdown.get_analysis()['max']['drawdown'],\n",
    "             x.analyzers.sharpe.get_analysis()['sharperatio']\n",
    "            ]for x in back\n",
    "]\n",
    "par_df = pd.DataFrame(par_list, columns=['Total returns', 'APR', 'drawdown', 'sharperatio'])\n",
    "print(par_df)\n",
    "cerebro.plot(style='candle')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
